////////////////////////////////////////////////////////////////////////////////
//
//  This program is free software; you can redistribute it and/or modify 
//  it under the terms of the GNU Lesser General Public License as published 
//  by the Free Software Foundation; either version 3 of the License, or (at 
//  your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but 
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
//  License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License 
//  along with this program; if not, see <http://www.gnu.org/copyleft/lesser.html>.
//
////////////////////////////////////////////////////////////////////////////////

package merapi.gps;

import java.io.IOException;
import java.io.InputStream;

import merapi.gps.driver.IGPSDriver;
import merapi.gps.messages.GPSMessage;
import merapi.gps.model.GPSConfig;
import merapi.gps.model.GPSInfo;
import merapi.gps.parser.IGPSParser;
import merapi.gps.parser.SimpleNMEAParser;
import merapi.gps.usglobalstat.bu353.driver.USGlobalStat_BU_353_Driver;

/**
 *  The GPSProxy is a proxy to the gps hardware (via the IGPSDriver). 
 *  
 *  @author Adam Flater - adamflater.net
 */
public class GPSProxy 
{
    //--------------------------------------------------------------------------
    //
    //  Class properties
    //
    //--------------------------------------------------------------------------

	/**
	 *  The driver to use to connect / disconnect the GPS device
	 */
	public static IGPSDriver driver = new USGlobalStat_BU_353_Driver();
	
	/**
	 *  Parses the data read from the GPD hardware
	 */
	public static IGPSParser parser = new SimpleNMEAParser();
	
	
	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Starts (connects to) the hardware.
	 */
	public static void start()
	{
		if ( driver != null )
		{
			driver.connect();
	
			GPSReaderThread __gpsReaderThread = new GPSReaderThread( driver.getInputStream(), 
																	 __gpsConfig, parser );
			__gpsReaderThread.start();
		}
	}
	
	/**
	 *  Stops (disconnects from) the hardware
	 */
	public static void stop()
	{
		if ( driver != null )
		{
			driver.disconnect();
		}
		GPSProxy.__gpsReaderThread.stopRunning = true;
	}
	
	/**
	 *  Sets the config for the proxy.
	 */
	public static void setConfiguration( GPSConfig __gpsConfig )
	{
		GPSProxy.__gpsConfig = __gpsConfig;
		GPSProxy.__gpsReaderThread.gpsConfig = __gpsConfig;
	}
	

	//--------------------------------------------------------------------------
	//
	//  Class variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @protected
	 *  
	 *  The thread responsible for reading and parsing data from the gps device
	 */
	protected static GPSReaderThread __gpsReaderThread = null;
	
	/**
	 *  @protected
	 *  
	 *  The config data used to send update messages
	 */
	protected static GPSConfig __gpsConfig = null;
}

/**
 *  The GPSReaderThread reads data from the inputStream using the gpsParser to parse
 *  data into IGPSInfo... which is sent via the GPSMessage 
 */
class GPSReaderThread extends Thread
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     */
	public GPSReaderThread( InputStream inputStream, GPSConfig gpsConfig, IGPSParser gpsParser )
	{
		this.gpsConfig = gpsConfig;
		this.gpsParser = gpsParser;
		
		__inputStream = inputStream;
	}
	
	
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

	/**
	 *  The config for the GPSReader
	 */
	public GPSConfig gpsConfig = null;
	
	/**
	 *  Parses the data read from the gps hardware
	 */
	public IGPSParser gpsParser = null;
	
	/**
	 *  While stopRunning is false, the reader thread continues to read and parse.
	 */
	public boolean stopRunning = false;
	
	
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
	
	/**
	 *  The threading api run method (called by Thread.start())
	 */
	public void run() 
	{
		while( stopRunning == false )
		{
			try
			{
				int in = __inputStream.read();
				gpsParser.append( (char)in );
				
				if ( gpsParser.hasMoreElements() )
				{
					updateGPSInfo( gpsParser.nextGPSInfo() );
				}
			}
			catch ( IOException e )
			{
				e.printStackTrace();
			}
		}
	}
	
	/**
	 *  @protected
	 *  
	 *  Called when a new line has been parsed.
	 */
	protected void updateGPSInfo( GPSInfo newGPSInfo )
	{
		//System.out.println( newGPSInfo );
		
		long time = System.currentTimeMillis();
		boolean sendUpdate = false;

		if ( gpsConfig != null )
		{
			sendUpdate = ( gpsConfig.getNotifyOnTimeout() == true && 
				 		   ( time - __lastUpdate ) > gpsConfig.getTimeout() ) ||
				 		 ( gpsConfig.getNotifyOnDelta() == true && 
				 		   ( Math.abs( __gpsInfo.getLatitude() - newGPSInfo.getLatitude() ) > 
				 		   		gpsConfig.getLatitudeDelta() ) &&
				 		   ( Math.abs( __gpsInfo.getLongitude() - newGPSInfo.getLongitude() ) > 
				 		   		gpsConfig.getLongitudeDelta() ) );
		}
		
		__gpsInfo = newGPSInfo;
		
		if ( sendUpdate == true || sendUpdate == false )
		{
			GPSMessage message = new GPSMessage( GPSMessage.GPS_DATA );
			message.setGpsInfo( (GPSInfo)__gpsInfo );
			message.send();
		}
		
		__lastUpdate = time;
	}


	//--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

	/**
	 *  @protected
	 *  
	 *  The modeled gps data.
	 */
	protected GPSInfo __gpsInfo = null;
	
	/**
	 *  @protected
	 *  
	 *  The last time an update GPSMessage was sent
	 */
	protected long __lastUpdate = System.currentTimeMillis();

	/**
	 *  @protected
	 *  
	 *  The inputStream that raw gps data is read from.
	 */
	protected InputStream __inputStream = null;

}
